#include "ioqueue.h"

#include "debug.h"
#include "sync.h"
#include "thread.h"

void ioqueue_init(struct ioqueue* queue)
{
  mutex_init(&queue->lock);
  queue->consumer_blocked = queue->producer_blocked = NULL;
  queue->head = queue->tail = 0;
}

char ioqueue_getchar(struct ioqueue* queue)
{
  while (ioqueue_is_empty(queue))
  {
    mutex_acquire(&queue->lock);
    ioqueue_wait(&queue->consumer_blocked);
    mutex_release(&queue->lock);
  }

  mutex_acquire(&queue->lock);

  char c = queue->buf[queue->tail];
  queue->tail = ioqueue_next_pos(queue->tail);

  if (queue->producer_blocked != NULL)
  {
    ioqueue_wake_up(&queue->producer_blocked);
  }

  mutex_release(&queue->lock);

  return c;
}

void ioqueue_putchar(struct ioqueue* queue, char byte)
{
  while (ioqueue_is_full(queue))
  {
    mutex_acquire(&queue->lock);
    ioqueue_wait(&queue->producer_blocked);
    mutex_release(&queue->lock);
  }

  mutex_acquire(&queue->lock);

  queue->buf[queue->head] = byte;
  queue->head = ioqueue_next_pos(queue->head);

  if (queue->consumer_blocked != NULL)
  {
    ioqueue_wake_up(&queue->consumer_blocked);
  }

  mutex_release(&queue->lock);
}

void ioqueue_wait(struct task_struct** waiter_thread)
{
  ASSERT(*waiter_thread == NULL && waiter_thread != NULL);

  *waiter_thread = cur_thread_PCB_get();
  thread_block(TASK_BLOCKED);
}

void ioqueue_wake_up(struct task_struct** waiter_thread)
{
  {
    ASSERT(*waiter_thread == NULL && waiter_thread != NULL);

    thread_unblock(*waiter_thread);
    *waiter_thread = NULL;
  }
}

int32_t ioqueue_next_pos(int32_t cur_pos) { return (cur_pos + 1) % BUF_SIZE; }

bool ioqueue_is_full(struct ioqueue* queue)
{
  ASSERT(intr_get_status() == INTR_OFF);
  return queue->tail == ioqueue_next_pos(queue->head);
}

bool ioqueue_is_empty(struct ioqueue* queue)
{
  ASSERT(intr_get_status() == INTR_OFF);
  return queue->head == queue->tail;
}
